fix: unrunnable CLI binary, lock poisoning on failed translation, deletion persistence, locale matching - #3
Merged
Conversation
…etion persistence, locale matching
- Remove duplicate shebang: src/cli.ts had its own #!/usr/bin/env node on top
of the tsup banner, so dist/cli.js started with two shebang lines — a syntax
error under node and bun that made the published binary unrunnable. CI now
smoke-tests dist/cli.js --help under both runtimes.
- Extract shared lock/sync logic into src/lock.ts (used by both the CLI and
the Vite plugin) so lock semantics can no longer diverge:
- Lock entries are committed only for keys whose batches succeeded for ALL
target locales. Failed batches no longer poison the lock (previously a
failed run wrote empty target files plus a fully-populated lock, and
reruns reported "No changes detected").
- On any batch failure the CLI exits non-zero naming the failed locales and
keys; the Vite plugin throws and fails the build.
- Deleted source keys are pruned from target locale files and the lock even
when there is nothing to translate (previously the early return skipped
all writes and removed strings lived in target files forever).
- CLI-written lock entries now preserve context (previously dropped, which
made mixed CLI + Vite autoExtract usage re-translate every
context-annotated key).
- detectLocale now matches available locales case-insensitively and returns
the canonical casing (browser "pt-br" matches available "pt-BR").
- The ai package is imported lazily inside translateBatch/translateMarkdown,
so loading the Vite plugin for extract-only or fresh-lock builds works
without ai installed. Peer range corrected to ">=3.0.0 <5.0.0" (the code
uses LanguageModelV1, removed in ai v5).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes six verified bugs in v1.1.0, spanning the CLI binary, lock-file integrity, deletion handling, locale detection, and the
aidependency contract.1. Unrunnable CLI binary (blocker)
Before:
src/cli.tscarried its own#!/usr/bin/env nodeANDtsup.config.tsadded a banner shebang, sodist/cli.jsstarted with two shebang lines — a syntax error under both node and bun. The published 1.1.0solid-translatebinary could not run at all.After: the source shebang is removed (the tsup banner is the single source of truth), and CI now smoke-tests
node dist/cli.js --helpandbun dist/cli.js --helpafter every build so this can never regress.2. Lock poisoning on failed translation (blocker)
Before: lock entries were recorded for keys before translation ran, and batch failures were caught-and-continued — then target files and the lock were written anyway. A failed run (e.g. missing API key) wrote
es.jsonas{}plus a fully-populated lock; the rerun said "No changes detected". Silent, permanent corruption.After: the diff produces pending lock entries that are committed only for keys whose batches succeeded for all target locales. Successful batches are still written, but on any failure the CLI exits non-zero naming the failed locales/keys, and the Vite plugin throws (fails the build) with the same detail. The lock never claims a key is translated when it isn't; failed keys are retried on the next run.
3. Deleted keys never persisted
Before: when the only change was deleted source keys, the "no changed keys" early return skipped all writes, so removed strings lived in target locale files (and shipped bundles) forever.
After: a deletions-only run prunes the deleted keys from every target file and writes the updated lock — with zero AI calls.
4. CLI dropped
contextfrom lock entriesBefore: CLI-written lock entries omitted
contextwhile Vite-written ones included it, so mixingsolid-translate translatewith ViteautoExtractre-translated every context-annotated key on each tool switch.After: the CLI preserves existing lock contexts when rewriting entries (and passes them as hints to the translator). Bonus: the Vite plugin with
autoExtract: falseno longer wipes contexts either.Fixes 2–4 are single-sourced: the previously duplicated (and divergent) lock/diff/write logic in
cli.tsandvite.tsnow lives in a sharedsrc/lock.ts(diffLock+syncLocaleFiles), used by both entry points.5. Locale detection case bug
Before:
detectLocalelowercased the browser locale but compared againstavailableLocalesverbatim — an available"pt-BR"never matched a browser"pt-br"/"pt-BR"and fell through to the default.After: matching is case-insensitive and returns the canonical casing from
availableLocales("pt-br"→"pt-BR").6. Lazy
aiimport + correct peer rangeBefore:
dist/vite.jsstatically importedgenerateObjectfromai, so merely loading the Vite plugin requiredaiinstalled even for extract-only or fresh-lock builds. The peer rangeai >=3.0.0also admitted ai v5, which removed theLanguageModelV1type this package uses.After: every
aiimport is a lazyawait import("ai")executed only when translation actually runs (verified: the plugin loads and completes a fresh-lockbuildStartwithnode_modules/airemoved). Peer range is now">=3.0.0 <5.0.0".Tests
tests/lock.test.ts: 20 tests covering diffing, lock-poisoning/retry behavior, per-batch and per-locale failure isolation, deletion-only pruning, CLI↔Vite context parity, and corrupted-lock recovery (stubbed translator, real temp-dir fs).tests/locale-detect.test.ts: 5 new case-sensitivity/canonical-casing tests.bun run build,bunx tsc --noEmit, and node+bun smoke ofdist/cli.jsall green.No public API changes.
src/extract.tsand the virtual-module design are untouched.🤖 Generated with Claude Code